#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <UIKit/UIApplication.h>
#import <LockInfo/Plugin.h>

#import "framework/FWCommon.h"
#import "framework/FWApplication.h"
#import "framework/FWApplicationDelegate.h"
#import "framework/FWPreferences.h"
#import "framework/FWListView.h"
#import "framework/FWDetailListView.h"
#import "framework/FWApplication.h"

#import "PIIntegration.h"
#import "PITaskIntegration.h"
#import "PITask.h"

#import "PIPlugin.h"
#import "PICommon.h"
#import "PITodoListAccess.h"
#import "PITodoView.h"
#import "PITodoPreview.h"
#import "PITodoStatus.h"
#import "PITodoPlugin.h"
#import "PIContext.h"


@implementation PITodoListAccess


// ******************************************************************************************
#pragma mark -
#pragma mark Fetching data from the database

- (NSArray*)application:(FWApplication*)application fetchNewDataForPrefs:(FWPreferences*)prefs
{
    NSBundle* bundle = [NSBundle bundleForClass:[self class]];
    PITodoStatus* status = (PITodoStatus*)application.info;
    
    // Read current display settings
    application.fwPlugin.debugMode = ([prefs intValue:kPIOptionsDebugCode withDefault:kPIOptionsDebugCodeDefault] == kDebugModeCode);
    status.shortDates = [prefs intValue:kPIOptionsShortDates withDefault:kPIOptionsShortDatesDefault];
    status.showIcons = [prefs boolValue:kPIOptionsShowIcons withDefault:kPIOptionsShowIconsDefault];
    status.showContexts = [prefs intValue:kPIOptionsShowContexts withDefault:kPIOptionsShowContextsDefault];
    status.singleLineOniPad = [prefs boolValue:kPIOptionsiPadSingleLine withDefault:kPIOptionsiPadSingleLineDefault];
    status.showEmoji = [prefs boolValue:kPIOptionsShowEmoji withDefault:kPIOptionsShowEmojiDefault]; 
    status.lowColour = [prefs boolValue:kPIOptionsLowColourDisplay withDefault:kPIOptionsLowColourDisplayDefault];
    
    // Save the current setting of the show details flag
    NSInteger showDetails = [prefs intValue:kPIOptionsShowDates withDefault:kPIOptionsShowDatesDefault];
    if (showDetails != status.showListDetails) {
        DebugLog(@"LI:PITodo: Details display changed - reset toggle mode");
        status.toggleOn = NO;
        status.showListDetails = showDetails;
    }
    
    // If using automatic contexts then get the latest values
    if (status.isAutoContextOn) {
        // Update the context update count if there is one
        NSInteger changeCount = -1;
        if (UIPasteboard* pasteBoard = [UIPasteboard pasteboardWithName:PIIntegrationContextPasteboard create:NO])
            changeCount = [pasteBoard changeCount];
        
        // If change count has changed then record the new value and get the latest contexts
        if (changeCount != status.autoContextPasteboardUpdateCount) {
            status.autoContextPasteboardUpdateCount = changeCount;
            [application setAttribute:kPIApplicationContextCurrent value:[PIIntegration locationContextsFromPI]];
        }
    }
    
    FWDatabase* database = nil;
    @try {
        // Connect to the database once only
        database = FWDBConnectDatabase(application.databasePath, application);
        
        // Get the current data
        NSArray* newTodos = [PITaskIntegration basicTaskList:application database:database prefs:prefs key:nil parentKey:nil search:nil];
        
        NSArray* contextList = [application getAttribute:kPIApplicationContextCurrent];
        BOOL contextMode = NO;
        if (contextList) {
            NSMutableString* contextNames = [NSMutableString stringWithCapacity:80];
            for (PIContext* context in contextList) {
                [contextNames appendString:context.title withSeparator:@", "];
            }
            
            if (contextNames.length > 0) {
                ((PITodoStatus*)application.info).captionValue = contextNames;
                contextMode = YES;
            }
            else
                ((PITodoStatus*)application.info).captionValue = localize(bundle, @"Context/Label/None");
        }
        
        // Examine the data set
        BOOL contextFound = NO;
        NSInteger returnedCount = [newTodos count];
        NSInteger filteredCount = 0;
        NSInteger detailsCount = 0;
        
        // Check if any of the selected contexts were found in the dataset
        for (PITask* element in newTodos) {
            if (element.contextClassification == ContextClassificationInContext)
                contextFound = YES;
            
            if (element.hasDetails)
                ++detailsCount;
        }
        
        NSInteger hideNoContext = [prefs intValue:kPIOptionsHideNoContext withDefault:kPIOptionsHideNoContextDefault];
        NSInteger hideOutOfContext = [prefs intValue:kPIOptionsHideOutOfContext withDefault:kPIOptionsHideOutOfContextDefault];
        if (contextFound ||
            (contextMode && ((hideNoContext == FilterContextOptionsHideAlways) || (hideOutOfContext == FilterContextOptionsHideAlways)))) {
            NSMutableArray* filteredList = [NSMutableArray arrayWithCapacity:[newTodos count]];
            NSMutableArray* lastList = [NSMutableArray arrayWithCapacity:[newTodos count]];
            BOOL contextAtStart = [prefs boolValue:kPIOptionsShowContextAtStart withDefault:kPIOptionsShowContextAtStartDefault];
            
            XLog(@"LI:PITodo: Applying context mode filters");
            
            for (PITask* element in newTodos) {
                NSInteger context = element.contextClassification;
                BOOL filter = NO;
                
                if (context == ContextClassificationInContext) {
                    // Do nothing
                }
                else if (context == ContextClassificationOutOfContext) {
                    if (hideOutOfContext == FilterContextOptionsHideAlways) {
                        DebugLog(@"LI:PITodo: Filtering out of context always - %@", element.title);
                        filter = YES;
                    }
                    else if (hideOutOfContext == FilterContextOptionsHideIfMatch) {
                        DebugLog(@"LI:PITodo: Filtering out of context since match found - %@", element.title);
                        filter = contextFound;
                    }
                }
                else if (context == ContextClassificationNoContext) {
                    if (hideNoContext == FilterContextOptionsHideAlways) {
                        DebugLog(@"LI:PITodo: Filtering no context always - %@", element.title);
                        filter = YES;
                    }
                    else if (hideNoContext == FilterContextOptionsHideIfMatch) {
                        DebugLog(@"LI:PITodo: Filtering no context since match found - %@", element.title);
                        filter = contextFound;
                    }
                }
                
                if (filter) {
                    // Update totals to incidate am item is now missing
                    if (element.hasDetails)
                        --detailsCount;
                    ++filteredCount;
                }
                else {
                    if ((context == ContextClassificationInContext) || !contextAtStart)
                        [filteredList addObject:element];
                    else
                        [lastList addObject:element];
                }
            }
            
            // If we have any items we've moved down the list then add them back to the end
            if ([lastList count] > 0)
                [filteredList addObjectsFromArray:lastList];
            
            XLog(@"LI:PITodo: Filtered task list; old size = %d; new size = %d", newTodos.count, filteredList.count);
            
            // Use the filtered list
            newTodos = filteredList;
        }
        
        status.itemsReturnedCount = returnedCount;
        status.itemsWithDetailsCount = detailsCount;
        status.itemsFilteredCount = filteredCount;
        
        // If we have already calculated the child counts in order to filter parents then we don't need to do it again
        BOOL calcChildren = [prefs intValue:kPIOptionsShowParent withDefault:kPIOptionsShowParentDefault] == HideParentIfChildren;
        if (!calcChildren && [prefs boolValue:kPIOptionsShowParentIcons withDefault:kPIOptionsShowParentIconsDefault])
            [PITaskIntegration addChildDataToTaskList:application database:database parents:newTodos prefs:prefs];
        else
            XLog(@"LI:PITodo: Child counts by SQL");
        
        // Use the original data list
        return newTodos;
    }
    @finally {
        FWDBDisconnectDatabase(database);
    }
    
    // Could not access database
    return [NSArray array];
}

- (BOOL)application:(FWApplication*)application shouldUpdateForDate:(NSDate*)date
{
    if (((PITodoStatus*)application.info).isAutoContextOn) {
        if (UIPasteboard* pasteBoard = [UIPasteboard pasteboardWithName:PIIntegrationContextPasteboard create:NO]) {
            NSInteger lastUpdates = ((PITodoStatus*)application.info).autoContextPasteboardUpdateCount;
            NSInteger newUpdates = [pasteBoard changeCount];
            if (newUpdates > lastUpdates) {
                XLog(@"LI:PITodo: Context list changed - old=%i, new=%i - update required", lastUpdates, newUpdates);
                return YES;
            }
        }
    }
    
    return NO;
}    


// ******************************************************************************************
#pragma mark -
#pragma mark Describing the layout

- (NSInteger)tableView:(LITableView*)tableView totalNumberOfItems:(FWApplication*)application preferences:(FWPreferences*)prefs
{
    PITodoStatus* info = (PITodoStatus*)application.info;
    return info.itemsReturnedCount;
}

- (BOOL)shouldShowDetailLine:(PITask*)task application:(FWApplication*)app preferences:(FWPreferences*)prefs forItem:(BOOL)forItem
{
    int showDate = ((PITodoStatus*)app.info).showListDetails;
    BOOL toggleActive = ((PITodoStatus*)app.info).isToggleOn;
    
    if (showDate == ShowDatesAlways) {
        // If always showing dates then show unless the toggle is present
        return !toggleActive;
    }
    else if (showDate == ShowDatesNever) {
        // If never showing dates then don't show unless the toggle is present
        return toggleActive;
    }
    else if (toggleActive) {
        // Field is optional. If toggle is active then if no details were present for any
        // item then withoug the toggle, nothing was shown. So, show all details as the toggle
        // state. Otherwise show no details as the toggle state as some items must have
        // been shown without the toggle.
        return ((PITodoStatus*)app.info).itemsWithDetailsCount == 0;
    }
    
    if (forItem)
        return task.hasDetails;
    else
        return NO;
}

- (CGFloat)tableView:(LITableView *)tableView heightForItem:(NSObject *)item application:(FWApplication *)application preferences:(FWPreferences *)prefs
{
    PITask* task = (PITask*)item;
    BOOL showDetail = [self shouldShowDetailLine:task application:application preferences:prefs forItem:YES];
    BOOL singleLine = ((PITodoStatus*)application.info).isSingleLineOniPad;
    
    if (FWIsDeviceAnIPad() && singleLine) {
        return [PITodoView heightOfSummaryRowWithFont:tableView.theme.summaryStyle.font
                                           withHeader:NO
                                     withFollowingRow:NO];
    }
    else {
        CGFloat summaryHeight = [PITodoView heightOfSummaryRowWithFont:tableView.theme.summaryStyle.font
                                                            withHeader:NO
                                                      withFollowingRow:showDetail];
        if (showDetail) {
            CGFloat detailHeight = [PITodoView heightOfDetailRowWithFont:tableView.theme.detailStyle.font];
            return summaryHeight + detailHeight;
        }
        else
            return summaryHeight;
    }
}


// ******************************************************************************************
#pragma mark -
#pragma mark Providing data for the display

- (FWListView*)application:(FWApplication*)app cellViewForTableView:(LITableView*)tableView  preferences:(FWPreferences*)prefs
{
    // We can hard-code the height at this time as this will vary dynamically depending upon content
    return [[PITodoView alloc] initWithFrame:CGRectMake(0, 0, tableView.bounds.size.width, 35) table:tableView application:app];
}

- (void)tableView:(LITableView*)tableView setDetailsUsingView:(FWListView*)view forItem:(NSObject *)item application:(FWApplication *)app preferences:(FWPreferences *)prefs
{
    PITodoStatus* status = (PITodoStatus*)app.info;
    
    if (item) {
        PITask* task = (PITask*)item;
        
        BOOL dateLine = [self shouldShowDetailLine:task application:app preferences:prefs forItem:YES];
        BOOL showOverdue = [prefs boolValue:kPIOptionsHighlightOverdue withDefault:kPIOptionsHighlightOverdueDefault];
        BOOL showChildIcons = [prefs boolValue:kPIOptionsShowParentIcons withDefault:kPIOptionsShowParentIconsDefault];
        kPITaskViewStyle style = (kPITaskViewStyle)[prefs intValue:kPITaskOptionsViewStyle withDefault:kPITaskOptionsViewStyleDefault];
        
        [(PITodoView*)view setViewForTable:tableView
                                  fromTask:task
                               application:app
                                     style:style
                            showShortDates:(ShortDatesOptions)status.shortDates
                            showDetailLine:dateLine
                                 showIcons:status.isShowIcons
                                 showEmoji:status.isShowEmoji
                               showOverdue:showOverdue
                            showChildIcons:showChildIcons
                                 lowColour:status.isLowColour];
        
        
        // Do we want everything on one line for an iPad?
        if ((view.singleLine = FWIsDeviceAnIPad()) && status.isSingleLineOniPad) {
            if (status.showListDetails != ShowDatesIfNeeded)
                view.expandSingleLine = ![self shouldShowDetailLine:task application:app preferences:prefs forItem:NO];
            else if (status.isToggleOn)
                view.expandSingleLine = status.itemsWithDetailsCount != 0;
            else
                view.expandSingleLine = status.itemsWithDetailsCount == 0;
        }
    }
    else {
        PITodoPlugin* todo = (PITodoPlugin*)app.fwPlugin;
        view.summaryText.text = [NSString stringWithFormat:@"update=%@", FWDebugTimeStringForUTCDate(todo.lastUpdate)];
        view.detailText.text = [NSString stringWithFormat:@"items=%i; filtered=%i; details=%i",
                                status.itemsReturnedCount,
                                status.itemsFilteredCount,
                                status.itemsWithDetailsCount];
    }
}

- (NSString*)tableView:(LITableView*)tableView detailForHeaderInSection:(NSInteger)section application:(FWApplication*)app preferences:(FWPreferences*)prefs
{
    NSBundle* bundle = [NSBundle bundleForClass:[self class]];
    NSInteger showContexts = ((PITodoStatus*)app.info).showContexts;
    
    if ([prefs boolValue:kPIOptionsShowAllTasks withDefault:kPIOptionsShowAllTasksDefault])
        return localize(bundle, @"Filters/Disabled");
    
    if (showContexts == ShowContextsNever)
        return nil;
    else if (((PITodoStatus*)app.info).isAutoContextOn) {
        NSArray* contexts = (NSArray*)[app getAttribute:kPIApplicationContextCurrent];
        if (!contexts || ([contexts count] == 0))
            return localize(bundle, @"Context/Auto/0");
        else if ([contexts count] == 1)
            return localize(bundle, @"Context/Auto/1");
        else
            return [NSString stringWithFormat:localize(bundle, @"Context/Auto/X"), [contexts count]]; 
    }
    else if (((PITodoStatus*)app.info).isFixedContextOn) {
        NSArray* contexts = (NSArray*)[app getAttribute:kPIApplicationContextCurrent];
        if (!contexts || ([contexts count] == 0))
            return localize(bundle, @"Context/Manual/0");
        else if ([contexts count] == 1)
            return localize(bundle, @"Context/Manual/1");
        else
            return [NSString stringWithFormat:localize(bundle, @"Context/Manual/X"), [contexts count]]; 
    }
    else if (showContexts == ShowContextsAlways)
        return localize(bundle, @"Context/Off");
    else 
        return nil;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Implement preview display

- (UIView*)tableView:(LITableView*)tableView previewWithFrame:(CGRect)rect atIndex:(NSInteger)index forItems:(NSArray*)list application:(FWApplication*)app preferences:(FWPreferences*)prefs
{   
    LIPlugin* plugin = (LIPlugin*)[app.fwPlugin liPlugin];
    BOOL show = [FWPreferences boolValue:kPIOptionsShowPreviews withDefault:kPIOptionsShowPreviewsDefault preferences:plugin.preferences];
    if (!show)
        return nil;
    
    PITodoPreview* previewData = [[[PITodoPreview alloc] init] autorelease];
    FWDetailListView *thePreview = [[[FWDetailListView alloc] initWithFrame:rect
                                                                       list:list
                                                                      index:index
                                                                    caption:@"ListView/Item %i/%i"
                                                                application:app
                                                                   delegate:previewData] autorelease];
    thePreview.backgroundColor = [UIColor whiteColor];
    
    return thePreview;
}


// ******************************************************************************************
#pragma mark -
#pragma mark Libstatusbar support

- (NSString*)libstatusbarIconName
{
    return @"PIPlugin";
}

- (BOOL)showLibstatusbarIconForItems:(NSArray*)items preferences:(FWPreferences *)prefs
{
    if ([prefs boolValue:kPIOptionsShowStatusbarIcons withDefault:kPIOptionsShowStatusbarIconsDefault]) {
        for (PITask* task in items) {
            if (task.isInProgress)
                return YES;
        }
    }
    
    return NO;
}

@end
